namespace ScrumSprintMonitor.Implementation.Configuration
{
	#region #using Directives

	using System;
	using System.Collections.Generic;
	using System.Globalization;
	using System.IO;
	using System.Xml;
	using System.Xml.Schema;

	using Infrastructure;

	using Ninject.Core;
	using Ninject.Core.Parameters;

	#endregion

	public class XmlStreamBasedTeamInfoProvider : DataStreamObjectSerializer,
	                                              ITeamMemberInfoProvider
	{
		private readonly IDictionary<string, ITeamMemberInformation> dictionary = new Dictionary<string, ITeamMemberInformation>();
		private readonly IKernel kernel;
		private readonly ILogger<XmlStreamBasedTeamInfoProvider> logger;
		private readonly IDataStreamProvider xsdStreamProvider;
		private ITeamMemberInformation unassignedTeamMemberInfo;

		public XmlStreamBasedTeamInfoProvider(IKernel kernel, IDataStreamProvider xmlStreamProvider, IDataStreamProvider xsdStreamProvider,
		                                      ILogger<XmlStreamBasedTeamInfoProvider> logger)
			: base(xmlStreamProvider)
		{
			this.kernel = kernel;
			this.xsdStreamProvider = xsdStreamProvider;
			this.logger = logger;
		}

		protected override void ReadObjectFromStream()
		{
			if (Disposed)
			{
				throw new ObjectDisposedException(GetType().Name);
			}

			this.dictionary.Clear();

			try
			{
				using (var stream = this.streamProvider.OpenStream(false))
				{
					var settings = new XmlReaderSettings
					               	{
					               		Schemas = new XmlSchemaSet(),
					               		IgnoreWhitespace = false,
					               		IgnoreComments = true,
					               		ValidationType = ValidationType.Schema
					               	};
					if (this.xsdStreamProvider != null)
					{
						using (var xsdStream = this.xsdStreamProvider.OpenStream(false))
						{
							using (var xsdReader = XmlReader.Create(xsdStream))
							{
								settings.Schemas.Add(null, xsdReader);
							}
						}
					}

					using (var reader = XmlReader.Create(stream, settings))
					{
						double hourlyProductivityFactor = 1.0;
						bool dedicatedToSprint = true;
						string timeZone = TimeZoneInfo.Local.Id;
						DateTime workdayStart = new DateTime(2000, 1, 1, 9, 0, 0), workdayEnd = new DateTime(2000, 1, 1, 18, 0, 0);
						DateTime lunchHourStart = new DateTime(2000, 1, 1, 12, 0, 0), lunchHourEnd = new DateTime(2000, 1, 1, 13, 0, 0);
						bool triedActivatingAvatarProviderStore = false;
						IAvatarProviderStore avatarProviderStore = null;

						reader.Read();
						reader.ReadStartElement("team-members");

						while (!reader.EOF)
						{
							if (reader.ReadToFollowing("team-member"))
							{
								string name = null;
								bool unassigned = false;
								string displayAlias = string.Empty, schedulerAlias = string.Empty;
								string avatarProviderId = null, avatarKey = null;
								IAvatar avatar = null;

								do
								{
									if (reader.NodeType == XmlNodeType.Element)
									{
										switch (reader.Name)
										{
											case "team-member":
											{
												name = reader.GetAttribute("name");

												string unassignedStr = reader.GetAttribute("unassigned");
												if (!string.IsNullOrEmpty(unassignedStr))
												{
													unassigned = Convert.ToBoolean(unassignedStr, CultureInfo.InvariantCulture);
												}
												break;
											}
											case "hourly-productivity-factor":
												hourlyProductivityFactor = Convert.ToDouble(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												break;
											case "dedicated-to-sprint":
												dedicatedToSprint = reader.ReadElementContentAsBoolean();
												break;
											case "display-alias":
												displayAlias = reader.ReadString();
												break;
											case "scheduler-alias":
												schedulerAlias = reader.ReadString();
												break;
											case "time-zone":
												timeZone = reader.ReadString();
												break;
											case "workday":
												reader.ReadToDescendant("start");
												workdayStart = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadStartElement("end");
												workdayEnd = DateTime.Parse(reader.ReadContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadEndElement();
												break;
											case "lunch":
												reader.ReadToDescendant("start");
												lunchHourStart = DateTime.Parse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadStartElement("end");
												lunchHourEnd = DateTime.Parse(reader.ReadContentAsString(), CultureInfo.InvariantCulture);
												reader.ReadEndElement();
												break;
											case "avatar":
												avatarProviderId = reader.GetAttribute("provider");
												avatarKey = reader.GetAttribute("key");
												break;
										}
									}

									reader.Read();
								} while (reader.Depth == 2);

								if (!string.IsNullOrEmpty(avatarProviderId) && !string.IsNullOrEmpty(avatarKey))
								{
									if (avatarProviderStore == null && !triedActivatingAvatarProviderStore)
									{
										try
										{
											avatarProviderStore = this.kernel.Get<IAvatarProviderStore>();
										}
										catch (ActivationException e)
										{
											triedActivatingAvatarProviderStore = true;
										}
									}

									if (avatarProviderStore != null)
									{
										var provider = avatarProviderStore.GetProvider(avatarProviderId);
										if (provider != null)
										{
											avatar = provider.GetAvatar(avatarKey);
										}
									}
								}

								var parameters = new Dictionary<string, object>
								                 	{
								                 		{"name", name},
								                 		{"unassigned", unassigned},
								                 		{"displayAlias", displayAlias},
								                 		{"schedulerAlias", schedulerAlias},
								                 		{"timeZone", TimeZoneInfo.FindSystemTimeZoneById(timeZone)},
								                 		{"workdayStart", workdayStart},
								                 		{"workdayEnd", workdayEnd},
								                 		{"lunchHourStart", lunchHourStart},
								                 		{"lunchHourEnd", lunchHourEnd},
								                 		{"hourlyProductivityFactor", hourlyProductivityFactor},
								                 		{"dedicatedToSprint", dedicatedToSprint}
								                 	};
								var teamMemberInfo = this.kernel.Get<ITeamMemberInformation>(With.Parameters.ConstructorArguments(parameters));
								if (avatar != null)
								{
									teamMemberInfo.Avatar = avatar;
								}
								if (unassigned)
								{
									this.unassignedTeamMemberInfo = teamMemberInfo;
								}
								else
								{
									this.dictionary.Add(name, teamMemberInfo);
								}
							}
						}
					}
				}
			}
			catch (IOException e)
			{
				this.logger.Warn(e, "Exception detected while reading team information configuration.");

				throw;
			}

			AsyncRaisePropertyChanged(null); // This may get called from a non-UI thread
		}

		#region Implementation of ITeamMemberInfoProvider

		public ITeamMemberInformation GetTeamMemberInformation(string teamMemberName)
		{
			EnsureObjectIsRead();

			if (string.IsNullOrEmpty(teamMemberName))
			{
				return this.unassignedTeamMemberInfo;
			}

			ITeamMemberInformation info;
			if (this.dictionary.TryGetValue(teamMemberName, out info))
			{
				return info;
			}

			return null;
		}

		public IEnumerable<ITeamMemberInformation> TeamMembers
		{
			get
			{
				EnsureObjectIsRead();

				var list = new List<ITeamMemberInformation>(this.dictionary.Values);

				return list;
			}
		}

		#endregion
	}
}